<template>
  <div class="file-upload">
    <div class="file-upload-container" @click.prevent="triggerUpload" v-bind="$attrs">
      <slot v-if="fileStatus === 'loading'" name="loading">
        <button class="btn btn-primary">正在上传...</button>
      </slot>
      <slot v-else-if="fileStatus === 'success'" name="uploaded" :uploadedData="uploadedData">
        <button class="btn btn-primary">上传成功</button>
      </slot>
      <slot v-else name="default">
        <button class="btn btn-primary">点击上传</button>
      </slot>
    </div>
    <input ref="fileInput" type="file" class="file-input d-none" @change="handleFileChange" />
  </div>
</template>

<script lang="ts">
import { defineComponent, PropType, ref, watch } from 'vue';
import request from '../utils/request';
type UploadStatus = 'ready' | 'loading' | 'success' | 'error';
type CheckFunction = (file: File) => boolean;

export default defineComponent({
  name: 'Uploader',
  props: {
    action: {
      type: String,
      required: true,
    },
    beforeUpload: {
      type: Function as PropType<CheckFunction>,
    },
    uploaded: {
      type: Object,
    },
  },
  inheritAttrs: false,
  emits: ['file-uploaded', 'file-uploaded-error'],
  setup(props, ctx) {
    const fileInput = ref<null | HTMLInputElement>(null);
    const fileStatus = ref<UploadStatus>(props.uploaded ? 'success' : 'ready');
    const uploadedData = ref(props.uploaded);
    watch(
      () => props.uploaded,
      newValue => {
        if (newValue) {
          fileStatus.value = 'success';
          uploadedData.value = newValue;
        }
      },
    );
    const triggerUpload = () => {
      if (fileInput.value) {
        fileInput.value.click();
      }
    };

    const handleFileChange = (e: Event) => {
      const currentTarget = e.target as HTMLInputElement;
      if (currentTarget.files) {
        const files = Array.from(currentTarget.files);

        if (props.beforeUpload) {
          const result = props.beforeUpload(files[0]);
          if (!result) {
            // 注意一定清空 input 内容
            if (fileInput.value) {
              fileInput.value.value = '';
            }
            return;
          }
        }

        fileStatus.value = 'loading';

        // 注意 currentTarget.files 不是数组，是伪数组，也就是具备 length 属性
        const formData = new FormData();

        formData.append('file', files[0]);

        // 注意这里并不需要自定义headers，因为axios实现会判断是否data为Formdata的实例而自动增加'Content-Type': 'multipart/form-data'
        request
          .post(props.action, formData)
          .then(res => {
            fileStatus.value = 'success';
            uploadedData.value = res.data;
            ctx.emit('file-uploaded', res.data);
          })
          .catch(err => {
            fileStatus.value = 'error';
            ctx.emit('file-uploaded-error', err);
          })
          .finally(() => {
            // 这一步是将input选中的文件 清除为空字符串
            if (fileInput.value) {
              fileInput.value.value = '';
            }
          });
      }
    };

    return {
      fileInput,
      triggerUpload,
      fileStatus,
      handleFileChange,
      uploadedData,
    };
  },
});
</script>

<style lang="scss" scoped></style>
